using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public static class AssetLoader
{
    private static readonly HandlePool handles = new();
#if UNITY_EDITOR
    private static Dictionary<string, bool> assetCache = new();
    private static bool enalbeRandomWaitTime = true;
#endif
    
    public static async UniTask<T> Load<T>(object belong, string key)
    {
        var code = belong.GetHashCode();
        return await Load<T>(code, key);
    }
    
    public static async UniTask<T> Load<T>(int code, string key)
    {
#if UNITY_EDITOR
        // 模拟加载比较卡的情况
        if (enalbeRandomWaitTime)
        {
            if (!assetCache.ContainsKey(key))
            {
                await UniTask.WaitForSeconds(Random.Range(0.1f, 0.5f));
                assetCache[key] = true;
            }
        }
#endif
        var handle = handles.GetHandle<T>(code, key);
        return await handle.Task.AsUniTask();
    }
    
    public static async UniTask<IList<T>> LoadByLabel<T>(object belong, string label) where T : Object
    {
#if UNITY_EDITOR
        // 模拟加载比较卡的情况
        if (!assetCache.ContainsKey(label))
        {
            await UniTask.WaitForSeconds(Random.Range(0.1f, 0.35f));
            assetCache[label] = true;
        }
#endif
        var code = belong.GetHashCode();
        var handle = handles.GetHandleByLabel<T>(code, label);
        return await handle.Task.AsUniTask();
    }

    public static void Release(object belong)
    {
        var code = belong.GetHashCode();
        Release(code);
    }

    public static void Release(int code)
    {
        handles.Release(code);
    }

    
    private class HandleRefCount
    {
        public AsyncOperationHandle handle;
        public int count;
        public ITypeHandlePool typeHandlePool;
    }

    /// <summary>
    /// 如果使用泛型HandlePool&lt;T&gt;，在调用Addressables.Release(handle);的时候，handle会被识别为object，导致调用的不是public static void Release(AsyncOperationHandle handle)
    /// 而是public static void Release&lt;TObject&gt;(TObject obj)
    /// </summary>
    private class HandlePool
    {
        private readonly Dictionary<int, HashSet<string>> codeAssets = new();
        private readonly Dictionary<string, HandleRefCount> handleRefCounts = new();
        private readonly Stack<HashSet<string>> pool = new();
        private readonly Stack<HandleRefCount> poolOfHandleRefCount = new();

        private void AddHandle(int code, string key, AsyncOperationHandle handle, ITypeHandlePool typeHandlePool)
        {
            if (!handleRefCounts.TryGetValue(key, out var handleRef))
            {
                handleRef = poolOfHandleRefCount.Count > 0
                    ? poolOfHandleRefCount.Pop()
                    : new HandleRefCount();
                handleRef.handle = handle;
                handleRef.count = 0;
                handleRef.typeHandlePool = typeHandlePool;
                handleRefCounts[key] = handleRef;
            }
            
            if (!codeAssets.TryGetValue(code, out var set))
            {
                set = GetHashSet();
                codeAssets[code] = set;
            }

            // 同一个code只允许加载一次相同的资源
            if (set.Add(key))
            {
                handleRef.count++;
            }
        }

        public AsyncOperationHandle<T> GetHandle<T>(int code, string key) 
        {
            var typeHandlePool = TypeHandlePool<T>.Instance;
            var handle = typeHandlePool.GetHandle(key);
            AddHandle(code, key, handle, typeHandlePool);
            return handle;
        }
        
        public AsyncOperationHandle<IList<T>> GetHandleByLabel<T>(int code, string label) 
        {
            var typeHandlePool = TypeHandlePool<T>.Instance;
            var handle = typeHandlePool.GetHandleByLabel(label);
            AddHandle(code, label, handle, typeHandlePool);
            return handle;
        }

        public void Release(int code)
        {
            if (codeAssets.TryGetValue(code, out var set))
            {
                foreach (var key in set)
                {
                    handleRefCounts[key].count--;
#if UNITY_EDITOR
                    Debug.Log($"Remove {key} leftCount: {handleRefCounts[key].count}");    
#endif
                    if (handleRefCounts[key].count <= 0)
                    {
                        Debug.Log($"Remove {key}");
                        Addressables.Release(handleRefCounts[key].handle);
                        handleRefCounts[key].typeHandlePool.Remove(key);
                        
                        poolOfHandleRefCount.Push(handleRefCounts[key]);
                        handleRefCounts.Remove(key);
                    }
                }
                set.Clear();
                PutHashSet(set);
                codeAssets.Remove(code);
            }
        }
        
        private HashSet<string> GetHashSet()
        {
            return pool.Count > 0 ? pool.Pop() : new HashSet<string>();
        }

        private void PutHashSet(HashSet<string> set)
        {
            pool.Push(set);
        }
    }

    private interface ITypeHandlePool
    {
        public void Remove(string code);
    }
    
    private class TypeHandlePool<T> : ITypeHandlePool
    {
        private static TypeHandlePool<T> _inst;
        public static TypeHandlePool<T> Instance
        {
            get
            {
                if (_inst == null)
                {
                    _inst = new TypeHandlePool<T>();
                }

                return _inst;
            }
        }
        private Dictionary<string, AsyncOperationHandle<T>> handles = new();
        private Dictionary<string, AsyncOperationHandle<IList<T>>> listHandles = new();

        public AsyncOperationHandle<T> GetHandle(string key)
        {
            if (!handles.TryGetValue(key, out var handle))
            {
                 handle = Addressables.LoadAssetAsync<T>(key);
                 handles[key] = handle;
            }
            return handle;
        }

        public AsyncOperationHandle<IList<T>> GetHandleByLabel(string label)
        {
            if (!listHandles.TryGetValue(label, out var handle))
            {
                var lbl = new AssetLabelReference
                {
                    labelString = label
                };
                handle = Addressables.LoadAssetsAsync<T>(lbl, null);
                listHandles[label] = handle;
            }
            return handle;
        }

        public  void Remove(string key)
        {
            handles.Remove(key);
            listHandles.Remove(key);
        }
    }
}
